﻿#region

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using OMS.Ice.T4Generator.Syntax;

#endregion


namespace OMS.Ice.T4Generator
{
    internal abstract class CodeBuilder
    {
        private readonly static Regex LineBreaks = new Regex( "[^\\r\\n]*\\r?\\n?", RegexOptions.Compiled );

        private string _code;
        private IEnumerable<string> _imports;
        private IEnumerable<Parameter> _parameters;

        protected IDictionary<IncludeDirective, IEnumerable<Part>> IncludedTemplates { get; set; }

        public string GeneratorType
        {
            get { return string.Format( "{0}.{1}", Namespace, ClassName ); }
        }

        public string Namespace { get; private set; }


        public string ClassName { get; private set; }


        private ParserResult ParserResult { get; set; }


        private IEnumerable<Part> Parts
        {
            get { return ParserResult.Parts; }
        }

        public string Generate( ParserResult parserResult)
        {
            ParserResult = parserResult;
            IncludedTemplates = parserResult.IncludedTemplates;

            Analyse();
            Generate();

            return _code;
        }

        private void Analyse()
        {
            GetClassName();
            GetImports();
            GetParameters();
        }


        private void GetClassName()
        {
            var fullName = (from p in Parts
                            where p is TemplateDirective
                            select ((TemplateDirective)p).ClassName).First();

            if( string.IsNullOrEmpty( fullName ) )
            {
                ClassName = ParserResult.TemplateName;
                Namespace = "OMS.Ice.T4Generator";
            }
            else
            {
                var index = fullName.LastIndexOf( '.' );

                ClassName = fullName.Substring( index + 1 );
                Namespace = fullName.Substring( 0, index );
            }
        }


        private void GetImports()
        {
            // Get all imports from main template.
            var imports = (from p in Parts
                           where p is ImportDirective
                           select ((ImportDirective)p).Namespace).ToList();


            // Add all imports from included templates.
            imports = imports.Union( from i in IncludedTemplates.Values
                                     from p in i
                                     where p is ImportDirective
                                     select ((ImportDirective)p).Namespace ).ToList();

            _imports = imports;
        }

        private void GetParameters()
        {
            _parameters = GetParameters( Parts );
        }

        protected static IEnumerable<Parameter> GetParameters( IEnumerable<Part> parts )
        {
            return (from p in parts
                    where p is ParameterDirective
                    let param = (ParameterDirective)p
                    select new Parameter {Name = param.Name, Type = param.Type}).ToList();
        }

        private void Generate()
        {
            var imports = CreateImports( _imports );
            var fields = CreateFields( _parameters );
            var parameters = CreateParameters( _parameters );
            var initializers = CreateFieldInitializers( _parameters );
            var implementation = CreateImplementation( Parts );
            var features = CreateFeatures();
            var includes = CreateIncludeMethods();


            _code = GetCodeTemplate();

            _code = _code.Replace( "<%namespace%>", Namespace );
            _code = _code.Replace( "<%classname%>", ClassName );
            _code = _code.Replace( "<%baseclassname%>", "OMS.Ice.T4Generator.T4Generator" );
            _code = _code.Replace( "<%imports%>", imports.ToString() );
            _code = _code.Replace( "<%fields%>", fields.ToString() );
            _code = _code.Replace( "<%parameters%>", parameters.ToString() );
            _code = _code.Replace( "<%fieldinitializations%>", initializers.ToString() );
            _code = _code.Replace( "<%implementation%>", implementation.ToString() );
            _code = _code.Replace( "<%features%>", features.ToString() );
            _code = _code.Replace( "<%includes%>", includes.ToString() );
        }

        private StringBuilder CreateImplementation( IEnumerable<Part> parts )
        {
            var allParts = (from p in parts
                            where p is StandardControlBlock || p is TextBlock || p is ExpressionControlBlock || p is IncludeDirective
                            select p).ToList();

            var result = new StringBuilder();

            foreach( var part in allParts )
            {
                if( part is StandardControlBlock )
                {
                    result.Append(CreateLinePragma(part));
                    result.Append(((StandardControlBlock)part).Content);
                }
                else if( part is TextBlock )
                {
                    result.Append(CreateLinePragma(part));
                    CreateTextBlock(result, CreateTextLines(((TextBlock)part).Content));
                }
                else if( part is ExpressionControlBlock )
                {
                    result.Append(CreateLinePragma(part));
                    CreateInlineCode(result, ((ExpressionControlBlock)part).Content);
                }
                else if( part is IncludeDirective  )
                {
                    CreateIncludeCode(result, (IncludeDirective)part);
                }
            }

            return result;
        }

        private void CreateIncludeCode(StringBuilder result, IncludeDirective directive)
        {
            if(directive.Mode == IncludeMode.Method)
            {
                result.Append(CreateLinePragma(directive));
                CreateMethodCall(result, directive);
            }
            else
            {
                result.Append( CreateImplementation( IncludedTemplates[directive] ).ToString());
            }
        }

        private StringBuilder CreateFeatures()
        {
            var parts = (from p in Parts
                         where p is FeatureControlBlock
                         select (FeatureControlBlock)p).ToList();

            parts.AddRange( from i in IncludedTemplates.Values
                            from p in i
                            where p is FeatureControlBlock
                            select (FeatureControlBlock)p );

            var result = new StringBuilder();

            foreach( var part in parts )
                result.Append( part.Content );

            return result;
        }

        private StringBuilder CreateIncludeMethods()
        {
            var result = new StringBuilder();
            foreach( var include in IncludedTemplates )
            {
                if (include.Key.Mode == IncludeMode.Inline)
                    continue;

                var content = GetIncludeTemplate();
                var parameters = CreateParameters( GetParameters( include.Value ) );
                content = content.Replace( "<%templatename%>", include.Key.Name );
                content = content.Replace( "<%parameters%>", parameters.ToString() );
                content = content.Replace( "<%include%>", CreateImplementation( include.Value ).ToString() );
                result.Append( content );
            }


            return result;
        }


        private IEnumerable<string> CreateTextLines( string content )
        {
            var result = (from Match match in LineBreaks.Matches( content )
                          where !string.IsNullOrEmpty( match.Value )
                          select match.Value.Replace( "\r", @"\r" ).Replace( "\n", @"\n" )).ToList();

            return result;
        }

        protected abstract string GetCodeTemplate();

        protected abstract string GetIncludeTemplate();

        protected abstract StringBuilder CreateImports( IEnumerable<string> imports );

        protected abstract StringBuilder CreateFields( IEnumerable<Parameter> parameters );

        protected abstract StringBuilder CreateParameters( IEnumerable<Parameter> parameters );

        protected abstract StringBuilder CreateCallParameters( IEnumerable<Parameter> parameters );

        protected abstract StringBuilder CreateFieldInitializers( IEnumerable<Parameter> parameters );

        protected abstract StringBuilder CreateTextBlock( StringBuilder result, IEnumerable<string> content );

        protected abstract StringBuilder CreateInlineCode( StringBuilder result, string statement );

        protected abstract StringBuilder CreateMethodCall( StringBuilder result, IncludeDirective directive );

        protected abstract string CreateLinePragma( Part part );


        #region Nested type: Parameter

        protected struct Parameter
        {
            public string Name;
            public string Type;
        }

        #endregion
    }
}